﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ObviousCode.Aspect.PluginLibrary.Interfaces;
using ObviousCode.UI.Aspect.PluginLibrary.Interfaces;
using ObviousCode.Common.Utils;
using ObviousCode.Aspect.PluginLibrary.Collections;
using ObviousCode.Aspect.PluginLibrary.Abstract;
using ObviousCode.Common.Utils.Library.Set;
using ObviousCode.Aspect.PluginLibrary;
using ObviousCode.Aspect.PluginLibrary.Args;
using ObviousCode.Aspect.PluginLibrary.Exceptions;
using ObviousCode.UI.Aspect.ExceptionRendering;
using System.Drawing.Drawing2D;
using ObviousCode.Common.Utils.Library;
using ObviousCode.Common.Utils.Library.Dictionaries;
using ObviousCode.UI.Aspect.Persistence.AspectSurface.Savers;
using ObviousCode.Common.Utils.Library.Collections;
using System.Runtime.InteropServices;
using ObviousCode.Aspect.PluginLibrary.Caches;

namespace ObviousCode.UI.Aspect
{
    public partial class AspectSurface : UserControl, IAspectSurface
    {
		#region Fields (13) 

        AspectChartObjectCollection _collection;
        bool _drawArrowsOnRightClick;
        PointF _drawingArrowEnd;
        PointF _drawingArrowStart;
        IntPtr _dwnd;
        IExceptionReporter _exceptionReporter;
        ISurfaceObject _invalidatedItem;
        bool _isDirty = false;
        LasooStateHandler _lasooState;
        MouseBehaviourState _mouseBehaviour;        
        int _refreshUndoRedo;
        object _wndProcLock = new object();
        Stack<Type> _selectionTypeStack;

		#endregion Fields 

		#region Enums (1) 

        public enum MouseBehaviourState { SurfaceObjectBehaviour = 0, ArrowBehaviour = 1}

		#endregion Enums 

		#region Constructors (1) 

        public AspectSurface()
        {
            InitializeComponent();

            _refreshUndoRedo = RegisterWindowMessage("refresh-undo");

            HandleCreated += new EventHandler(AspectSurface_HandleCreated);

            this.SetStyle(
                  ControlStyles.AllPaintingInWmPaint |
                  ControlStyles.UserPaint |
                  ControlStyles.DoubleBuffer, true);

            Reset();
        }

		#endregion Constructors 

		#region Properties (4) 

        public AspectChartObjectCollection Collection
        {
            get { return _collection; }
        }

        public bool DrawArrowsOnRightClick
        {
            get { return _drawArrowsOnRightClick; }
            set 
            { 
                _drawArrowsOnRightClick = value;
                if (DrawArrowsOnRightClickStateChanged != null)
                {
                    DrawArrowsOnRightClickStateChanged(this, EventArgs.Empty);
                }
            } 
        }

        public MouseBehaviourState MouseBehaviour
        {
            get { return _mouseBehaviour; }
            set
            {
                _mouseBehaviour = value;
                if (MouseBehaviourStateChanged != null)
                {
                    MouseBehaviourStateChanged(this, EventArgs.Empty);
                }
            }
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IList<ISurfaceObject> Selection
        {
            get
            {
                return _collection.Selection;
            }
            set
            {
                _collection.Selection = value;
            }
        }

		#endregion Properties 

		#region Delegates and Events (4) 

		// Events (4) 

        public event EventHandler DrawArrowsOnRightClickStateChanged;

        public event EventHandler MouseBehaviourStateChanged;        

        public event EventHandler SelectionChanged;

		#endregion Delegates and Events 

		#region Methods (35) 

		// Public Methods (5) 

        public void AddSurfaceObject(ISurfaceObject item)
        {
            item.RefreshRequested += new EventHandler(item_RefreshRequested);
            item.DragStarted += new EventHandler(item_DragStarted);
            item.Dragging += new EventHandler<EventArgs<PointF>>(item_Dragging);
            item.DragCompleted += new EventHandler(item_DragCompleted);

            _collection.Add(item);

            Selection = new List<ISurfaceObject> { item };
            Focus();

            _isDirty = true;

            Refresh();
        }

        public void AddSurfaceRelationship(ISurfaceRelationship relationship)
        {
            _collection.Add(relationship);

            relationship.SurfaceObjectPositionRequested += new EventHandler<ObviousCode.Aspect.PluginLibrary.Event.AspectObjectBoundsRequestEventArgs>(relationship_SurfaceObjectPositionRequested);

            _isDirty = true;

            Refresh();
        }

        public new void Load(AspectChartObjectCollection collection)
        {
            _collection = collection;
            Refresh();
        }

        public PropertyDictionary Persist()
        {
            return SurfaceSaver.LatestVersion.Save(this);
        }

        public void Render(Graphics graphics)
        {
            try
            {
                if (_invalidatedItem != null)
                {
                    _collection.UpdateBounds(_invalidatedItem, _invalidatedItem.Render(graphics));

                    _invalidatedItem = null;

                    return;
                }

                _collection.ClearBounds();

                if (_lasooState.IsLasooing)
                {
                    RectangleF lasoo = _lasooState.LasooBounds;
                    graphics.DrawRectangle(new Pen(Color.LightGray), new Rectangle((int)lasoo.X, (int)lasoo.Y, (int)lasoo.Width, (int)lasoo.Height));
                }
                else if (MouseBehaviour == MouseBehaviourState.ArrowBehaviour &&
                    (Control.MouseButtons == MouseButtons.Left || (DrawArrowsOnRightClick && Control.MouseButtons == MouseButtons.Right)))
                {
                    DrawArrow(graphics, _drawingArrowStart, _drawingArrowEnd);
                }

                foreach (ISurfaceObject item in _collection.Items)
                {
                    RectangleF bounds = item.Render(graphics);

                    RectangleF inflatedBounds = new RectangleF(bounds.Location, bounds.Size);

                    inflatedBounds.Inflate(new SizeF(2f, 4f));

                    _collection.RegisterItemBounds(item, inflatedBounds);
                }

                foreach (ISurfaceRelationship relationship in _collection.Relationships)
                {
                    relationship.Render(graphics);
                }
            }
            catch (AspectRenderingException ex)
            {
                _exceptionReporter.ReportException(ex);
            }
        }

        public void Reset()
        {
            _isDirty = false;

            _selectionTypeStack = new Stack<Type>();

            if (_collection != null && !DesignMode)
            {
                _collection.SelectionChanged -= new EventHandler(_collection_SelectionChanged);
                _collection.CollectionChanged -= new EventHandler(_collection_CollectionChanged);
            }

            _lasooState = new LasooStateHandler();

            _collection = new AspectChartObjectCollection();

            _exceptionReporter = new ExceptionReporter();

            _collection.SelectionChanged += new EventHandler(_collection_SelectionChanged);
            _collection.CollectionChanged += new EventHandler(_collection_CollectionChanged);
            _collection.UndoRedoChanged += new EventHandler(_collection_UndoRedoChanged);

            MouseBehaviour = MouseBehaviourState.SurfaceObjectBehaviour;

            DrawArrowsOnRightClick = true;

            _alignLeft.Enabled = false;
            _alignTop.Enabled = false;

            _undo.Enabled = false;
            _redo.Enabled = false;

            _properties.Visible = true;

            _showHideProperties.Checked = true;
            _drawRelationship.Visible = !DrawArrowsOnRightClick;
            _drawRelationshipSeparator.Visible = _drawRelationship.Visible;

            Invalidate();
        }
		// Protected Methods (1) 

        protected override void WndProc(ref System.Windows.Forms.Message message)
        {
            if (message.Msg == _refreshUndoRedo)
            {
                RefreshUndoRedoState();
            }

            base.WndProc(ref message);
        }
		// Private Methods (27) 

        private void _alignLeft_Click(object sender, EventArgs e)
        {
            float masterLeft = Selection[0].Position.X;

            for (int i = 1; i < Selection.Count; i++)
            {
                Selection[i].SetPosition(new PointF(masterLeft, Selection[i].Position.Y), false);                
            }

            Refresh();
        }

        private void _alignTop_Click(object sender, EventArgs e)
        {
            float masterTop = Selection[0].Position.Y;

            for (int i = 1; i < Selection.Count; i++)
            {
                Selection[i].SetPosition(new PointF(Selection[i].Position.X, masterTop), false);
            }

            Refresh();
        }

        private void _arrowBehaviourToolStripButton_Click(object sender, EventArgs e)
        {
            MouseBehaviour = 
                MouseBehaviour == AspectSurface.MouseBehaviourState.ArrowBehaviour ? 
                AspectSurface.MouseBehaviourState.SurfaceObjectBehaviour : 
                AspectSurface.MouseBehaviourState.ArrowBehaviour;
        }

        void _collection_CollectionChanged(object sender, EventArgs e)
        {
            _isDirty = true;
        }

        void _collection_SelectionChanged(object sender, EventArgs e)
        {
            if (SelectionChanged != null)
            {
                SelectionChanged(this, e);
            }

            _alignLeft.Enabled = _collection.Selection.Count > 1;
            _alignTop.Enabled = _collection.Selection.Count > 1;

            _properties.SelectedObjects = ObjectArray.Cast(_collection.Selection.Select(s => s.Tag));
        }

        void _collection_UndoRedoChanged(object sender, EventArgs e)
        {
            lock (_wndProcLock)
            {
                PostMessage(new HandleRef(this, _dwnd), _refreshUndoRedo, IntPtr.Zero, IntPtr.Zero);
            }         
        }

        private void _redoButton_Click(object sender, EventArgs e)
        {
            _collection.Redo();

            Refresh();
        }

        private void _showHideProperties_Click(object sender, EventArgs e)
        {
            _properties.Visible = !_properties.Visible;

            _showHideProperties.Checked = _properties.Visible;
        }

        private void _undo_Click(object sender, EventArgs e)
        {
            _collection.Undo();

            Refresh();
        }

        private void AspectSurface_DragDrop(object sender, DragEventArgs e)
        {
            PropertyDictionary tag = e.Data.GetData(typeof(TreeNode)).As<TreeNode>().Tag.As<PropertyDictionary>();

            ISurfaceObject item = tag.Get<ISurfaceObject>("value");

            Point dropPoint = PointToClient(new Point(e.X, e.Y));

            item.SetPosition(new PointF((float)dropPoint.X, (float)dropPoint.Y), true);

            AddSurfaceObject(item);
        }

        private void AspectSurface_DragOver(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Move;
        }

        void AspectSurface_HandleCreated(object sender, EventArgs e)
        {
            _dwnd = Handle;
        }

        private void AspectSurface_MouseDown(object sender, MouseEventArgs e)
        {
            if (DrawArrowsOnRightClick)
            {
                MouseBehaviour = e.Button == MouseButtons.Right ? MouseBehaviourState.ArrowBehaviour : 
                    MouseBehaviourState.SurfaceObjectBehaviour;
            }

            if (MouseBehaviour == MouseBehaviourState.SurfaceObjectBehaviour)
            {
                if (_collection.ItemAt(e.Location) == null)
                {
                    _lasooState.RegisterMouseDownOnSurface(e);
                }

                HandleMouseEvent(AspectConstants.MouseDownKey, e);
            }
            else
            {
                _drawingArrowStart = e.Location;
            }
        }

        private void AspectSurface_MouseMove(object sender, MouseEventArgs e)
        {
            if (MouseBehaviour == MouseBehaviourState.SurfaceObjectBehaviour)
            {
                _lasooState.RegisterMouseMove(e);

                if (!HandleMouseEvent(AspectConstants.MouseMoveKey, e) && _lasooState.IsLasooing)
                {
                    Refresh();
                }
            }
            else if (e.Button == MouseButtons.Left || (DrawArrowsOnRightClick && e.Button == MouseButtons.Right))
            {
                _drawingArrowEnd = e.Location;

                Refresh();
            }
        }

        private void AspectSurface_MouseUp(object sender, MouseEventArgs e)
        {
            if (MouseBehaviour == MouseBehaviourState.SurfaceObjectBehaviour)
            {
                _lasooState.RegisterMouseUp(e);

                if (!HandleMouseEvent(AspectConstants.MouseUpKey, e) && _lasooState.IsLasooEnding)
                {
                    Refresh();
                }
            }
            else
            {
                ISurfaceObject source = _collection.ItemAt(_drawingArrowStart);
                ISurfaceObject destination = _collection.ItemAt(_drawingArrowEnd);

                CreateRelationship(source.Tag, destination.Tag);
            }
        }

        private void CreateRelationship(IAspectItem source, IAspectItem destination)
        {            
            ISurfaceRelationship relationship =
                AspectCache.Main.DefaultRelationshipFactory.BuildRelationship(source, destination, "default");

            AddSurfaceRelationship(relationship);
        }

        private void AspectSurface_Paint(object sender, PaintEventArgs e)
        {
            //if partial render ...
            //else

            Render(e.Graphics);
        }
        
        private void DrawArrow(Graphics graphics, PointF _drawingArrowStart, PointF _drawingArrowEnd)
        {
            IUIRelationshipTheme theme = AspectCache.Main.DefaultRelationshipFactory.Descriptors["default"].Theme;

            Pen p = new Pen(theme.LineColor, theme.LineThickness);
            
            p.StartCap = LineCap.RoundAnchor;
            p.EndCap = LineCap.ArrowAnchor;
            
            graphics.DrawLine(p, _drawingArrowStart, _drawingArrowEnd);
        }

        private MousePluginEventArgs GetMousePluginEventArgs(MouseEventArgs e)
        {
            MousePluginEventArgs args = new MousePluginEventArgs();

            args.IsLasooing = _lasooState.IsLasooing;
            args.LasooBounds = _lasooState.LasooBounds;

            args.MultipleSelection = _collection.Selection.Count > 1;

            args.AltKey = (Control.ModifierKeys & Keys.Alt) == Keys.Alt;
            args.ControlKey = (Control.ModifierKeys & Keys.Control) == Keys.Control;
            args.ShiftKey = (Control.ModifierKeys & Keys.Shift) == Keys.Shift;
            args.MousePosition = e.Location;
            args.MouseDown = (Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left;
            args.LasooEnding = _lasooState.IsLasooEnding;
            args.IsDragging = e.Button == MouseButtons.None ? false : _collection.IsDragging;

            return args;
        }

        /// <returns>Returns true if Refresh was initiated by mouse event</returns>
        private bool HandleMouseEvent(string eventKey, MouseEventArgs e)
        {            
            Set<object> eventResponses = new Set<object>();

            Point mouseLocation = e.Location;

            MousePluginEventArgs args = GetMousePluginEventArgs(e);

            List<ISurfaceObject> invalidatedChartObjects = new List<ISurfaceObject>();

            string hoveredInstance = _collection.ItemAt(mouseLocation) == null ? "" : _collection.ItemAt(mouseLocation).InstanceKey;

            foreach (ISurfaceObject item in _collection.Items)
            {
                args.IsInBounds = hoveredInstance == item.InstanceKey;

                if (item.SupportsEvent(eventKey))
                {
                    object eventResponse = item.HandleEvent(eventKey, args);

                    if (eventResponse.ToString() == AspectConstants.EventResponse_RepaintBounds)
                    {
                        invalidatedChartObjects.Add(item);
                    }
                    else if (eventResponse != null)
                    {
                        eventResponses.UnionUpdate(eventResponse);
                    }
                }
            }

            if (eventResponses.Contains(AspectConstants.EventResponse_Repaint))
            {
                Refresh();
                return true;
            }
            else if (invalidatedChartObjects.Count > 0)
            {
                foreach (ISurfaceObject item in invalidatedChartObjects)
                {
                    _invalidatedItem = item;
                    Invalidate(new Region(_collection.GetItemBounds(item)));
                }
            }

            return false;
        }

        void item_DragCompleted(object sender, EventArgs e)
        {
            _isDirty = true;
            _collection.ItemDragCompleted(sender as ISurfaceObject);
        }

        void item_Dragging(object sender, EventArgs<PointF> e)
        {
            _collection.ItemDragging(sender as ISurfaceObject, e.EventData);
        }

        void item_DragStarted(object sender, EventArgs e)
        {
            _collection.ItemDragStarted(sender as ISurfaceObject);
        }

        void item_RefreshRequested(object sender, EventArgs e)
        {
            Refresh();
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern bool PostMessage(HandleRef hwnd, int msg, IntPtr wparam, IntPtr lparam);

        private void RefreshUndoRedoState()
        {
            lock (_wndProcLock)
            {
                _undo.Enabled = _collection.HasUndoSteps;
                _redo.Enabled = _collection.HasRedoSteps;
            }
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern int RegisterWindowMessage(string msg);

        void relationship_SurfaceObjectPositionRequested(object sender, ObviousCode.Aspect.PluginLibrary.Event.AspectObjectBoundsRequestEventArgs e)
        {
            ISurfaceObject item = _collection.Items.Where(i => i.Tag == e.AspectItem).FirstOrDefault();

            if (item != null)
            {
                e.RequestedBounds = item.GetLastRenderedBounds(e.Graphics);
            }

        }
		// Internal Methods (2) 

        internal void InformOnSave()
        {
            _isDirty = false;
        }

                internal bool TestForDirtyData()
        {
            return _isDirty;
        }

		#endregion Methods         

        public void SelectNextGroup()
        {
            if (_selectionTypeStack.Count == 0)
            {
                LoadSelectionTypeStack();
            }

            if (_selectionTypeStack.Count == 0) return;

            SelectType(_selectionTypeStack.Pop());
        }        

        private void LoadSelectionTypeStack()
        {
            _selectionTypeStack = new Stack<Type>(
                _collection.Items.Select(i => i.Tag.GetType()).Distinct()
                );
        }

        public void SetSelectionOfAll(bool select, bool refresh)
        {
            foreach (ISurfaceObject item in _collection.Items)
            {
                item.Selected = select;
            }

            if (refresh) Refresh();
        }

        private void SelectType(Type type)
        {
            foreach (ISurfaceObject item in _collection.Items)
            {
                item.Selected = item.Tag.GetType() == type;
            }

            Refresh();
        }

        internal void SelectLastSurfaceObject()
        {
            ISurfaceObject item = GetLastSelection();

            SetSelectionOfAll(false, false);            
            
            if (item != null) item.Selected = true;

            Refresh();
        }        

        internal void SelectNextSurfaceObject()
        {
            ISurfaceObject item = GetNextSelection();

            SetSelectionOfAll(false, false);            
            
            if (item != null) item.Selected = true;

            Refresh();
        }

        private ISurfaceObject GetNextSelection()
        {
            if (
                Selection.Count == 0 ||
                Selection[0] == _collection.Items.Last()
                )
            {
                return _collection.Items.First();
            }

            int idx = _collection.Items.IndexOf(Selection[0]);

            return _collection.Items[idx + 1];
        }

        private ISurfaceObject GetLastSelection()
        {
            if (Selection.Count == 0 ||
                Selection[0] == _collection.Items.First()
                )
            {
                return _collection.Items.Last();
            }

            int idx = _collection.Items.IndexOf(Selection[0]);

            return _collection.Items[idx - 1];
        }
        
    }

}
